import common from '@ohos.app.ability.common';
import picker from '@ohos.file.picker';
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
import wantConstant from '@ohos.ability.wantConstant';
import { MediaBean } from '../bean/MediaBean';
import { StringUtils } from '../utils/StringUtils';
import { Log } from '../utils/Log';
import image from '@ohos.multimedia.image';
import fileUri from '@ohos.file.fileuri';
import fs from '@ohos.file.fs';

/**
 * 多媒体辅助类
 */
export class MediaHelper {
  private readonly TAG: string = 'MediaHelper';

  private mContext: common.Context;

  private BYTE_CONVERSION: number = 1024; // 字节转换
  private maxCompressedImageSize: number = 1;

  constructor(context: common.Context) {
    this.mContext = context;
  }

  /**
   * 选择图片
   */
  public selectPicture(): Promise<MediaBean> {

    try {
      //图库选择选项（设置参数）
      let photoSelectOptions = new picker.PhotoSelectOptions();
      //MIMEType：可选文件类型。默认图片+视频。
      photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
      //可选最大值。默认为50
      photoSelectOptions.maxSelectNumber = 1;
      //图库选择器对象。支撑选择图片、视频和保存~等场景。
      let photoPicker = new picker.PhotoViewPicker();
      //进行文件选择。传入事先设定好的option
      return photoPicker.select(photoSelectOptions)
        .then(async (photoSelectResult) => {
          //photoSelectResult：包括两个系统能力。
          //photoUri：以数组形式返回选择的媒体文件的uri
          //isOriginalPhoto：是否为原图
          Log.info(this.TAG, 'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(photoSelectResult));

          if (photoSelectResult && photoSelectResult.photoUris && photoSelectResult.photoUris.length > 0) {
            let filePath = photoSelectResult.photoUris[0];
            Log.info(this.TAG, 'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + filePath);

            // let filePath = await this.compressPicture(filePath0)

            return filePath; //拿到选择的图片uri
          }

        }).catch((err) => { //抓取前一步里可能存在的error
          Log.error(this.TAG, 'PhotoViewPicker.select failed with err: ' + err);
          return err;
        }).then(async (filePath) => {
          //通过uri获得文件其他信息并生成mediaBean
          const mediaBean = await this.buildMediaBean(filePath);
          //最终返回mediaBean
          return mediaBean;
        });
    } catch (err) {
      Log.error(this.TAG, 'PhotoViewPicker failed with err: ' + err);
      return Promise.reject(err);
    }
  }

  /**
   * 选择文件
   */
  public selectFile(): Promise<MediaBean> {
    try {
      let documentSelectOptions = new picker.DocumentSelectOptions();
      let documentPicker = new picker.DocumentViewPicker();
      return documentPicker.select(documentSelectOptions)
        .then((documentSelectResult) => {
          Log.info(this.TAG, 'DocumentViewPicker.select successfully, DocumentSelectResult uri: ' + JSON.stringify(documentSelectResult));

          if (documentSelectResult && documentSelectResult.length > 0) {
            let filePath = documentSelectResult[0];
            Log.info(this.TAG, 'DocumentViewPicker.select successfully, DocumentSelectResult uri: ' + filePath);
            return filePath;
          }

        }).catch((err) => {
          Log.error(this.TAG, 'PhotoViewPicker.select failed with err: ' + err);
          return err;
        }).then(async (filePath) => {

          const mediaBean = await this.buildMediaBean(filePath);
          return mediaBean;

        });
    } catch (err) {
      Log.error(this.TAG, 'PhotoViewPicker failed with err: ' + err);
      return Promise.reject(err);
    }
  }


  /**
   * 拍照
   */
  public async takePhoto(context: common.UIAbilityContext): Promise<MediaBean> {


    let want = {
      'uri': '',
      'action': wantConstant.Action.ACTION_IMAGE_CAPTURE,
      'parameters': {},
    };
    return context.startAbilityForResult(want)
      .then((result) => {
        Log.info(this.TAG, `startAbility call back , ${JSON.stringify(result)}`);
        if (result.resultCode === 0 && result.want && StringUtils.isNotNullOrEmpty(result.want.uri)) {
          //拍照成功
          Log.info(this.TAG, 'takePhoto successfully, takePhotoResult uri: ' + result.want.uri);
          return result.want.uri;
        }
      }).catch((error) => {
        Log.info(this.TAG, `startAbility error , ${JSON.stringify(error)}`);
        return error;
      }).then(async (uri: string) => {
        const mediaBean = await this.buildMediaBean(uri);
        return mediaBean;
      });
  }


  /**
   * 封装多媒体实体类
   *
   * @param uri 文件路径
   */
  private async buildMediaBean(uri: string): Promise<MediaBean> {

    if (StringUtils.isNullOrEmpty(uri)) {
      return null;
    }

    const mediaBean: MediaBean = new MediaBean();
    mediaBean.localUrl = uri;
    await this.appendFileInfoToMediaBean(mediaBean, uri);
    return mediaBean;
  }

  /**
   * 通过Uri查找所选文件信息，插入到MediaBean中
   * @param mediaBean
   * @param uri
   */
  private async appendFileInfoToMediaBean(mediaBean: MediaBean, uri: string) {

    if (StringUtils.isNullOrEmpty(uri)) {
      return;
    }
    let fileList: Array<mediaLibrary.FileAsset> = [];

    const parts: string[] = uri.split('/');
    const id: string = parts.length > 0 ? parts[parts.length - 1] : '-1';

    try {

      let media = mediaLibrary.getMediaLibrary(this.mContext);
      let mediaFetchOptions: mediaLibrary.MediaFetchOptions = {
        selections: mediaLibrary.FileKey.ID + '= ?',
        selectionArgs: [id],
        uri: uri
      };

      let fetchFileResult = await media.getFileAssets(mediaFetchOptions);
      Log.info(this.TAG, `fileList getFileAssetsFromType fetchFileResult.count = ${fetchFileResult.getCount()}`);
      fileList = await fetchFileResult.getAllObject();
      fetchFileResult.close();
      await media.release();

    } catch (e) {
      Log.error(this.TAG, "query: file data  exception ");
    }

    if (fileList && fileList.length > 0) {

      let fileInfoObj = fileList[0];
      Log.info(this.TAG, `file id = ${JSON.stringify(fileInfoObj.id)} , uri = ${JSON.stringify(fileInfoObj.uri)}`);
      Log.info(this.TAG, `file fileList displayName = ${fileInfoObj.displayName} ,size = ${fileInfoObj.size} ,mimeType = ${fileInfoObj.mimeType}`);

      mediaBean.fileName = fileInfoObj.displayName;
      mediaBean.size = fileInfoObj.size;
      mediaBean.contentType = fileInfoObj.mimeType;

    }
  }

  public async compressPicture(imageSourceApi: image.ImageSource): Promise<string> {

    // //根据uri创建imageSourceApi
    // let imageSource = image.createImageSource(fd);

    // 设置解码参数DecodingOptions，解码获取PixelMap图片对象。
    const decodingOptions: image.DecodingOptions = {
      editable: true, // 是否可编辑。当取值为false时，图片不可二次编辑，如crop等操作将失败。
      desiredPixelFormat: 3, // 解码的像素格式。3表示RGBA_8888。
    }
    // 创建pixelMap
    return imageSourceApi.createPixelMap(decodingOptions).then(async (originalPixelMap: image.PixelMap) => {
      // 压缩图片
      let showImage: CompressedImageInfo = await this.compressedImage(originalPixelMap, 0.8)
        return showImage;
      }).catch((err) => {
        console.error(this.TAG, `Failed to create PixelMap with error message: ${err.message}, error code: ${err.code}`);
        return Promise.reject(err);
      }).then(async (showImage: CompressedImageInfo)=>{
        // 获取压缩后的图片信息
        let compressedImageSrc = fileUri.getUriFromPath(showImage.imageUri);
        let compressedByteLength = showImage.imageByteLength;
        let afterCompressionSize = (compressedByteLength / this.BYTE_CONVERSION).toFixed(1);
        // 图片压缩后的大小如果未能达到指定压缩目标大小。提示修改代码中的图片缩小倍数（REDUCE_SCALE），以便进一步压缩图片大小。
        if (compressedByteLength / this.BYTE_CONVERSION > this.maxCompressedImageSize) {
          console.error(this.TAG," 需调整代码中的图片缩小倍数")
        }
        console.log(this.TAG," 压缩后的图片大小：",afterCompressionSize);
        return compressedImageSrc;
    }).catch((err) => {
      console.error(this.TAG, `Failed to create PixelMap with error message: ${err.message}, error code: ${err.code}`);
      return Promise.reject(err);
    });

  }

  /**
   * 图片压缩，保存
   * @param sourcePixelMap：原始待压缩图片的PixelMap对象
   * @param maxCompressedImageSize：指定图片的压缩目标大小，单位kb
   * @returns compressedImageInfo：返回最终压缩后的图片信息
   */
  async compressedImage(sourcePixelMap: image.PixelMap, maxCompressedImageSize: number): Promise<CompressedImageInfo> {
    // 创建图像编码ImagePacker对象
    const imagePackerApi = image.createImagePacker();
    // 定义图片质量参数
    const IMAGE_QUALITY = 0;
    // 设置编码输出流和编码参数。图片质量参数quality范围0-100。
    const packOpts: image.PackingOption = { format: "image/jpeg", quality: IMAGE_QUALITY };
    // 通过PixelMap进行编码。compressedImageData为打包获取到的图片文件流。
    let compressedImageData: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);
    // 压缩目标图像字节长度
    const maxCompressedImageByte = maxCompressedImageSize * this.BYTE_CONVERSION;
    // TODO 知识点：图片压缩。先判断设置图片质量参数quality为0时，packing能压缩到的图片最小字节大小是否满足指定的图片压缩大小。如果满足，则使用packing方式二分查找最接近指定图片压缩目标大小的quality来压缩图片。如果不满足，则使用scale对图片先进行缩放，采用while循环每次递减0.4倍缩放图片，再用packing（图片质量参数quality设置0）获取压缩图片大小，最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据。
    if (maxCompressedImageByte > compressedImageData.byteLength) {
      // 使用packing二分压缩获取图片文件流
      compressedImageData = await this.packingImage(compressedImageData, sourcePixelMap, IMAGE_QUALITY, maxCompressedImageByte);
    } else {
      // 使用scale对图片先进行缩放，采用while循环每次递减0.4倍缩放图片，再用packing（图片质量参数quality设置0）获取压缩图片大小，最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据
      let imageScale = 1; // 定义图片宽高的缩放倍数，1表示原比例。
      const REDUCE_SCALE = 0.2; // 图片缩小倍数
      // 判断压缩后的图片大小是否大于指定图片的压缩目标大小，如果大于，继续降低缩放倍数压缩。
      while (compressedImageData.byteLength > maxCompressedImageByte) {
        if (imageScale > 0) {
          // 性能知识点: 由于scale会直接修改图片PixelMap数据，所以不适用二分查找scale缩放倍数。这里采用循环递减0.4倍缩放图片，来查找确定最适
          // 合的缩放倍数。如果对图片压缩质量要求不高，建议调高每次递减的缩放倍数reduceScale，减少循环，提升scale压缩性能。
          imageScale = imageScale - REDUCE_SCALE; // 每次缩放倍数减0.4
          // 使用scale对图片进行缩放
          await sourcePixelMap.scale(imageScale, imageScale);
          // packing压缩
          compressedImageData = await this.packing(sourcePixelMap, IMAGE_QUALITY);
        } else {
          // imageScale缩放小于等于0时，没有意义，结束压缩。这里不考虑图片缩放倍数小于reduceScale的情况。
          break;
        }
      }
    }
    // 保存图片，返回压缩后的图片信息。
    const compressedImageInfo: CompressedImageInfo = await this.saveImage(compressedImageData);
    return compressedImageInfo;
  }

  /**
   * packing压缩
   * @param sourcePixelMap：原始待压缩图片的PixelMap
   * @param imageQuality：图片质量参数
   * @returns data：返回压缩后的图片数据
   */
  async packing(sourcePixelMap: image.PixelMap, imageQuality: number): Promise<ArrayBuffer> {
    const imagePackerApi = image.createImagePacker();
    const packOpts: image.PackingOption = { format: "image/jpeg", quality: imageQuality };
    const data: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);
    return data;
  }

  /**
   * packing二分方式循环压缩
   * @param compressedImageData：图片压缩的ArrayBuffer
   * @param sourcePixelMap：原始待压缩图片的PixelMap
   * @param imageQuality：图片质量参数
   * @param maxCompressedImageByte：压缩目标图像字节长度
   * @returns compressedImageData：返回二分packing压缩后的图片数据
   */
  async packingImage(compressedImageData: ArrayBuffer, sourcePixelMap: image.PixelMap, imageQuality: number, maxCompressedImageByte: number): Promise<ArrayBuffer> {
    // 图片质量参数范围为0-100，这里以10为最小二分单位创建用于packing二分图片质量参数的数组。
    const packingArray: number[] = [];
    const DICHOTOMY_ACCURACY = 10;
    // 性能知识点: 如果对图片压缩质量要求不高，建议调高最小二分单位dichotomyAccuracy，减少循环，提升packing压缩性能。
    for (let i = 0; i <= 100; i += DICHOTOMY_ACCURACY) {
      packingArray.push(i);
    }
    let left = 0; // 定义二分搜索范围的左边界
    let right = packingArray.length - 1; // 定义二分搜索范围的右边界
    // 二分压缩图片
    while (left <= right) {
      const mid = Math.floor((left + right) / 2); // 定义二分搜索范围的中间位置
      imageQuality = packingArray[mid]; // 获取二分中间位置的图片质量值
      // 根据传入的图片质量参数进行packing压缩，返回压缩后的图片文件流数据。
      compressedImageData = await this.packing(sourcePixelMap, imageQuality);
      // 判断查找一个尽可能接近但不超过压缩目标的压缩大小
      if (compressedImageData.byteLength <= maxCompressedImageByte) {
        // 二分目标值在右半边，继续在更高的图片质量参数（即mid + 1）中搜索
        left = mid + 1;
        // 判断mid是否已经二分到最后，如果二分完了，退出
        if (mid === packingArray.length - 1) {
          break;
        }
        // 获取下一次二分的图片质量参数（mid+1）压缩的图片文件流数据
        compressedImageData = await this.packing(sourcePixelMap, packingArray[mid + 1]);
        // 判断用下一次图片质量参数（mid+1）压缩的图片大小是否大于指定图片的压缩目标大小。如果大于，说明当前图片质量参数（mid）压缩出来的
        // 图片大小最接近指定图片的压缩目标大小。传入当前图片质量参数mid，得到最终目标图片压缩数据。
        if (compressedImageData.byteLength > maxCompressedImageByte) {
          compressedImageData = await this.packing(sourcePixelMap, packingArray[mid]);
          break;
        }
      } else {
        // 目标值不在当前范围的右半部分，将搜索范围的右边界向左移动，以缩小搜索范围并继续在下一次迭代中查找左半部分。
        right = mid - 1;
      }
    }
    return compressedImageData;
  }

  /**
   * 图片保存
   * @param compressedImageData：压缩后的图片数据
   * @returns compressedImageInfo：返回压缩后的图片信息
   */
  async saveImage(compressedImageData: ArrayBuffer): Promise<CompressedImageInfo> {
    let context = this.mContext;

    // 定义要保存的压缩图片uri。image_compression_after.jpeg表示压缩后的图片。
    let i = Math.random().toString(36).slice(-8);
    const compressedImageUri: string = context.filesDir + '/' + 'image_compression_after_'+i+'.jpg';
    try {
      const res = fs.accessSync(compressedImageUri);
      if (res) {
        // 如果图片image_compression_after.jpeg已存在，则删除
        fs.unlinkSync(compressedImageUri);
      }
    } catch (err) {
      console.error(this.TAG, `AccessSync failed with error message: ${err.message}, error code: ${err.code}`);
    }
    // TODO 知识点：保存图片。获取最终图片压缩数据compressedImageData，保存图片。
    // 压缩图片数据写入文件
    const file: fs.File = fs.openSync(compressedImageUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    fs.writeSync(file.fd, compressedImageData);
    fs.closeSync(file);
    // 获取压缩图片信息
    let compressedImageInfo: CompressedImageInfo = new CompressedImageInfo();
    compressedImageInfo.imageUri = compressedImageUri;
    compressedImageInfo.imageByteLength = compressedImageData.byteLength;
    return compressedImageInfo;
  }
}

// 压缩后的图片信息类，用于刷新显示压缩后的图片和图片字节长度
class CompressedImageInfo {
  imageUri: string = ""; // 压缩后图片保存位置的uri
  imageByteLength: number = 0; // 压缩后图片字节长度

}

